package crp.extensions.xjtu.sso;

import crp.core.department.domain.models.DepartmentEntity;
import crp.core.department.domain.repositories.DepartmentRepository;
import crp.core.faculty.domain.models.FacultyDepartmentEntity;
import crp.core.faculty.domain.models.FacultyEntity;
import crp.core.faculty.domain.repositories.FacultyDepartmentRepository;
import crp.core.faculty.domain.repositories.FacultyRepository;
import crp.core.shared.LifecycleStatus;
import crp.core.student.domain.models.StudentEntity;
import crp.core.student.domain.repositories.StudentRepository;
import crp.core.user.domain.models.UserEntity;
import crp.core.user.domain.repositories.UserRepository;
import crp.extensions.xjtu.sso.models.XJTUDepartmentInfo;
import crp.extensions.xjtu.sso.models.XJTUUserInfo;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

@ApplicationScoped
public class XJTUSSOUserPersistence {
    private final FacultyRepository facultyRepository;
    private final StudentRepository studentRepository;
    private final DepartmentRepository departmentRepository;
    private final FacultyDepartmentRepository facultyDepartmentRepository;
    private final UserRepository userRepository;

    @Inject
    public XJTUSSOUserPersistence(FacultyRepository facultyRepository,
                                  StudentRepository studentRepository,
                                  DepartmentRepository departmentRepository,
                                  FacultyDepartmentRepository facultyDepartmentRepository,
                                  UserRepository userRepository) {
        this.facultyRepository = facultyRepository;
        this.studentRepository = studentRepository;
        this.departmentRepository = departmentRepository;
        this.facultyDepartmentRepository = facultyDepartmentRepository;
        this.userRepository = userRepository;
    }


    @Transactional
    public Optional<UserEntity> persist(XJTUUserInfo userInfo) {
        Optional<FacultyEntity> mayFaculty = userInfo.getFacultyNo()
                .map(facultyNo -> persistFaculty(facultyNo, userInfo.getMemberName(), userInfo.getDepartments(facultyNo)));
        Optional<StudentEntity> mayStudent = userInfo.getStudentNo()
                .map(studentNo -> persistStudent(studentNo, userInfo.getMemberName()));
        if (mayFaculty.isEmpty() && mayStudent.isEmpty()) {
            return Optional.empty();
        }
        return Optional.of(loadUser(mayFaculty, mayStudent));
    }

    private FacultyEntity persistFaculty(String facultyNo, String name, List<XJTUDepartmentInfo> departments) {
        FacultyEntity faculty = loadFaculty(facultyNo, name);
        loadFacultyDepartments(faculty, departments);
        return faculty;
    }

    private StudentEntity persistStudent(String studentNo, String name) {
        return loadStudent(studentNo, name);
    }

    private StudentEntity loadStudent(String studentNo, String name) {
        return studentRepository.findByStudentNo(studentNo)
                .orElseGet(() -> {
                    StudentEntity student = new StudentEntity();
                    student.setStudentNo(studentNo);
                    student.setName(name);
                    studentRepository.persist(student);
                    return student;
                });
    }

    private UserEntity loadUser(Optional<FacultyEntity> mayFaculty,
                                Optional<StudentEntity> mayStudent) {
        Optional<UserEntity> mayFacultyUser = mayFaculty.flatMap(faculty -> userRepository.findByFacultyId(faculty.getId()));
        Optional<UserEntity> mayStudentUser = mayStudent.flatMap(student -> userRepository.findByStudentId(student.getId()));
        UserEntity user = mayFacultyUser.orElse(mayStudentUser.orElse(null));
        if (mayFacultyUser.isPresent() && mayStudentUser.isPresent()) {
            user.setFacultyId(mayFaculty.map(FacultyEntity::getId).orElse(null));
            user.setStudentId(mayStudent.map(StudentEntity::getId).orElse(null));
            userRepository.persist(user);
            return user;
        }
        if (mayFacultyUser.isEmpty() && mayStudentUser.isEmpty()) {
            return loadNewUser(mayFaculty, mayStudent);
        }
        return user;
    }

    private UserEntity loadNewUser(Optional<FacultyEntity> mayFaculty, Optional<StudentEntity> mayStudent) {
        String name = mayFaculty.map(FacultyEntity::getName)
                .orElse(mayStudent.map(StudentEntity::getName).orElse(null));
        UserEntity user = new UserEntity();
        user.setName(name);
        user.setFacultyId(mayFaculty.map(FacultyEntity::getId).orElse(null));
        user.setStudentId(mayStudent.map(StudentEntity::getId).orElse(null));
        userRepository.persist(user);
        return user;
    }

    private void loadFacultyDepartments(FacultyEntity faculty, List<XJTUDepartmentInfo> departments) {
        departments.stream()
                .map(this::loadDepartment)
                .forEach(department -> {
                    Optional<FacultyDepartmentEntity> mayFD = facultyDepartmentRepository.findBy(faculty, department);
                    if (mayFD.isPresent()) {
                        return;
                    }
                    FacultyDepartmentEntity fd = new FacultyDepartmentEntity();
                    fd.setFaculty(faculty);
                    fd.setDepartment(department);
                    facultyDepartmentRepository.persist(fd);
                });
    }

    private DepartmentEntity loadDepartment(XJTUDepartmentInfo dept) {
        return departmentRepository.findByCode(dept.getDeptCode())
                .orElseGet(() -> {
                    DepartmentEntity department = new DepartmentEntity();
                    department.setCode(dept.getDeptCode());
                    department.setName(dept.getDeptName());
                    department.setStatus(LifecycleStatus.ENABLED);
                    departmentRepository.persist(department);
                    return department;
                });
    }

    private FacultyEntity loadFaculty(String facultyNo, String name) {
        return facultyRepository.findByFacultyNo(facultyNo)
                .orElseGet(() -> {
                    FacultyEntity faculty = new FacultyEntity();
                    faculty.setFacultyNo(facultyNo);
                    faculty.setName(name);
                    facultyRepository.persist(faculty);
                    return faculty;
                });
    }
}
